#region (c) 2014 THN Solutions LLC. - All rights reserved
/*
Simplified BSD License (BSD)
Copyright (c) 2014, THN Solutions LLC. ( www.ThnSolutions.com )
Author: Nguyễn, M. Hải                 ( www.Minh-Hai.com     )
All rights reserved.

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

    * Redistributions of source code must retain the above copyright notice, this 
      list of conditions and the following disclaimer.

    * Redistributions in binary form must reproduce the above copyright notice, 
      this list of conditions and the following disclaimer in the documentation 
      and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
POSSIBILITY OF SUCH DAMAGE.
*/
#endregion

#region Record of Change
//             							CHANGE HISTORY
// -------------------------------------------------------------------------------------
// |   DATE    | DEVELOPER  | DESCRIPTION                                              |
// -------------------------------------------------------------------------------------
// | 09-Nov-09 | HuyHM      | First creation.                                          |
// -------------------------------------------------------------------------------------
//
#endregion

#region Record of Audit
//             							REVIEW HISTORY
// -------------------------------------------------------------------------------------
// |   DATE    | REVIEWER   | NOTES                                                    |
// -------------------------------------------------------------------------------------
// -------------------------------------------------------------------------------------
//
#endregion

#region Using directives
using System;
using System.Collections;
using System.ComponentModel;
using System.Drawing;

using Thn;
#endregion

namespace Thn.Interface.Vcl
{
    /// <summary>
    /// Strongly typed collection of <see cref="ListboxItem"/>.
    /// </summary>
    public class ListBoxItemList : IList, ICollection, IEnumerable
    {
        #region Fields
        /// <summary>
        /// internal data
        /// </summary>
        internal ListBoxItem[] mItems = null;
        /// <summary>
        /// Number of items currently in list
        /// </summary>
        internal int mCount = 0;

        /// <summary>
        /// Parent of all item in this list.
        /// </summary>
        internal ListBoxItem Parent = null;

        [NonSerialized]
        private object mSynchRoot;
        #endregion

        #region Parent Listbox
        /// <summary>
        /// Parent list box. This value should be assigned by List box control.
        /// And it should assign one time for the root items only.
        /// </summary>
        internal IListBoxHost mParentListbox = null;
        /// <summary>
        /// Gets parent list box that show this item.
        /// Each time call get value, it will recursive 
        /// finding the root element and then return back.
        /// </summary>
        [Browsable(false), DefaultValue(null)]
        public IListBoxHost ParentListBox
        {
            get
            {
                IListBoxHost result = null;
                if (mParentListbox != null)
                {
                    #region Yeah! This is root items.
                    result = mParentListbox;
                    #endregion
                }
                else
                {
                    #region Find up
                    if (Parent != null)
                    {
                        #region Get up recursive
                        if (this.Parent.mSiblings != null)
                        {
                            // get recursive parent list box.
                            result = this.Parent.mSiblings.ParentListBox;
                        }
                        #region Defensive
                        else
                        {
                            // this tree has not added to any parent.
                        }
                        #endregion
                        #endregion
                    }
                    #region Defensive tracing
                    else
                    {
                        // this is orphan tree.
                    }
                    #endregion
                    #endregion
                }
                return result;
            }
        }
        #endregion

        #region Capacity
        private int mCapacity;
        /// <summary>
        /// Gets current maximal capacity
        /// </summary>
        public int Capacity
        {
            get { return mCapacity; }
        }
        #endregion

        #region Ease methods
        /// <summary>
        /// Add a list box item with text.
        /// </summary>
        /// <param name="text">Title's text.</param>
        public int Add(string text)
        {
            return this.Add(new ListBoxItem(text));
        }

        /// <summary>
        /// Create a new item and add to collection
        /// </summary>
        public int Add(string text, object value)
        {

            return Add(new ListBoxItem(text, value));
        }

        /// <summary>
        /// Create a new item and add to collection
        /// </summary>
        public int Add(string text, bool isChecked)
        {
            return Add(new ListBoxItem(text, isChecked));
        }

        /// <summary>
        /// Create a new item and add to collection
        /// </summary>
        public int Add(string text, Image image)
        {
            return Add(new ListBoxItem(text, false, image));
        }

        /// <summary>
        /// Create a new item and add to collection
        /// </summary>
        public int Add(string text, bool Checked, Image image)
        {
            return Add(new ListBoxItem(text, Checked, image));
        }

        /// <summary>
        /// Create a new item and add to collection
        /// </summary>
        public int Add(string text, bool isChecked, Image image, object value)
        {
            return Add(new ListBoxItem(text, isChecked, image, value));
        }
        #endregion

        #region IList Members
        #region Add
        /// <summary>
        /// Adds an item to the list
        /// </summary>
        public int Add(ListBoxItem value)
        {
            //check if not enough space then allocate more
            if (mCount >= mCapacity)
            {
                //calculate new capacity
                int newCapacity;
                if (mCapacity < 1000) newCapacity = (int)(mCapacity * 2);
                else if (mCapacity < 5000) newCapacity = (int)(mCapacity * 1.5);
                else newCapacity = (int)(mCapacity * 1.2);

                //allocate new buffer
                ListBoxItem[] newItems = new ListBoxItem[newCapacity];

                //copy old values to new buffer
                mItems.CopyTo(newItems, 0);

                //assign the new bufer to current buffer
                mItems = newItems;
                mCapacity = newCapacity;
            }

            //assign the new value to the end of current buffer
            mItems[mCount] = value;
            mCount++;

            //assign siblings for new item
            value.mSiblings = this;
            if (ParentListBox != null)
            {
                // notify to listbox
                ParentListBox.NotifyItemAdded(this, value);
            }
            #region Defensive
            else
            {
                // do not need to notify.
            }
            #endregion
            return mCount - 1;
        }

        /// <summary>
        /// Adds an item to the list
        /// </summary>
        int IList.Add(object value)
        {
            return Add((ListBoxItem)value);
        }
        #endregion

        #region Clear
        /// <summary>
        /// Removes all items
        /// </summary>
        public void Clear()
        {
            // remove all sibling of items
            for (int i = 0; i < mCount; i++)
            {
                //remove it
                mItems[i].mSiblings = null;
            }

            Array.Clear(mItems, 0, mCount);
            mCount = 0;

            if (ParentListBox != null)
            {
                // notify to listbox
                ParentListBox.NotifyItemsCleared(this);
            }
            #region Defensive
            else
            {
                // do not need to notify.
            }
            #endregion
        }
        #endregion

        #region Contains
        /// <summary>
        /// Determines whether the list contains a specific value
        /// </summary>
        public bool Contains(object value)
        {
            if (value == null)
            {
                //search for the first occurence of null item
                for (int i = 0; i < mCount; i++)
                {
                    if (mItems[i] == null) return true;
                }
            }
            else
            {
                //search for the first match
                for (int i = 0; i < mCount; i++)
                {
                    if (value.Equals(mItems[i])) return true;
                }
            }

            return false;
        }
        #endregion

        #region Index Of
        /// <summary>
        /// Determines the index of a specific item
        /// </summary>
        public int IndexOf(ListBoxItem value)
        {
            if (value == null)
            {
                //search for the first occurence of null item
                for (int i = 0; i < mCount; i++)
                {
                    if (mItems[i] == null) return i;
                }
            }
            else
            {
                //search for the first match
                for (int i = 0; i < mCount; i++)
                {
                    if (value.Equals(mItems[i])) return i;
                }
            }

            return -1;
        }

        /// <summary>
        /// Determines the index of a specific item
        /// </summary>
        int IList.IndexOf(object value)
        {
            return IndexOf((ListBoxItem)value);
        }
        #endregion

        #region Insert
        /// <summary>
        /// Inserts an item to the list at the specified index
        /// </summary>
        public void Insert(int index, ListBoxItem value)
        {
            //check if not enough space then allocate more
            if (mCount >= mCapacity)
            {
                //calculate new capacity
                int newCapacity;
                if (mCapacity < 1000) newCapacity = (int)(mCapacity * 2);
                else if (mCapacity < 5000) newCapacity = (int)(mCapacity * 1.5);
                else newCapacity = (int)(mCapacity * 1.2);

                //allocate new buffer
                ListBoxItem[] newItems = new ListBoxItem[newCapacity];

                //copy old values from left of index to new buffer
                if (index > 0)
                {
                    Array.Copy(mItems, 0, newItems, 0, index);
                }

                //assign the new value to the index position of the new buffer
                newItems[index] = value;

                //copy old values from right of index to new buffer
                if (index < mCount)
                {
                    Array.Copy(mItems, index, newItems, index + 1, mCount - index);
                }

                //assign the new bufer to current buffer
                mItems = newItems;
                mCapacity = newCapacity;
                mCount++;
            }
            else
            {
                //copy old values from right of index to new buffer
                if (index < mCount)
                {
                    Array.Copy(mItems, index, mItems, index + 1, mCount - index);
                }

                //assign the new value to the index position of the new buffer
                mItems[index] = value;
                mCount++;
            }

            // assign sibling for new item
            value.mSiblings = this;

            if (ParentListBox != null)
            {
                // notify to listbox
                ParentListBox.NotifyItemAdded(this, value);
            }
            #region Defensive
            else
            {
                // do not need to notify.
            }
            #endregion
        }

        /// <summary>
        /// Inserts an item to the list at the specified index
        /// </summary>
        void IList.Insert(int index, object value)
        {
            Insert(index, (ListBoxItem)value);
        }
        #endregion

        #region Is Fixed Size
        /// <summary>
        /// Gets a value indicating whether the list has a fixed size.
        /// </summary>
        public bool IsFixedSize
        { get { return false; } }
        #endregion

        #region Is Read Only
        /// <summary>
        /// Gets a value indicating whether the list is read-only.
        /// </summary>
        public bool IsReadOnly
        { get { return false; } }
        #endregion

        #region Remove
        /// <summary>
        /// Removes the first occurrence of a specific object from the list.
        /// </summary>
        public void Remove(ListBoxItem value)
        {
            int index = IndexOf(value);
            if (index >= 0) RemoveAt(index);
        }

        /// <summary>
        /// Removes the first occurrence of a specific object from the list.
        /// </summary>
        void IList.Remove(object value)
        {
            int index = IndexOf((ListBoxItem)value);
            if (index >= 0) RemoveAt(index);
        }
        #endregion

        #region Remove At
        /// <summary>
        /// Removes the list item at the specified index
        /// </summary>
        public void RemoveAt(int index)
        {
            mCount--;

            ListBoxItem removedItem = mItems[index];
            if (index < mCount)
            {
                // make sure remove sibling.
                mItems[index].mSiblings = null;

                //copy old values from the right of index
                Array.Copy(mItems, index + 1, mItems, index, mCount - index);
            }
            mItems[mCount] = null;

            if (ParentListBox != null)
            {
                // notify to listbox
                ParentListBox.NotifyItemRemoved(this, removedItem);
            }
            #region Defensive
            else
            {
                // do not need to notify.
            }
            #endregion
        }
        #endregion

        #region Indexer
        /// <summary>
        /// Gets or sets the element at the specified index.
        /// </summary>
        public ListBoxItem this[int index]
        {
            get { return mItems[index]; }
            set
            {
                mItems[index] = value;
                if (mParentListbox != null)
                {
                    mParentListbox.NotifyItemModified(value);
                }
                #region Defensive tracing
                else
                {
                    // do not process on null data value.
                }
                #endregion
            }
        }

        /// <summary>
        /// Gets or sets the element at the specified index.
        /// </summary>
        object IList.this[int index]
        {
            get { return mItems[index]; }
            set
            {
                mItems[index] = (ListBoxItem)value;
                if (mParentListbox != null)
                {
                    mParentListbox.NotifyItemModified((ListBoxItem)value);
                }
                #region Defensive tracing
                else
                {
                    // do not process on null data value.
                }
                #endregion
            }
        }
        #endregion

        #endregion

        #region ICollection Members
        #region Copy To
        /// <summary>
        /// Copies the elements of the collection to an array
        /// </summary>
        /// <param name="array">The one-dimensional array that is the destination of the elements copied from collection. The array must have zero-based indexing.</param>
        public void CopyTo(Array array)
        {
            Array.Copy(mItems, 0, array, 0, mCount);
        }

        /// <summary>
        /// Copies the elements of the collection to an array, starting at a particular array index. 
        /// </summary>
        /// <param name="array">The one-dimensional array that is the destination of the elements copied from collection. The array must have zero-based indexing.</param>
        /// <param name="index">The zero-based index in array at which copying begins.</param>
        public void CopyTo(Array array, int index)
        {
            Array.Copy(mItems, 0, array, index, mCount);
        }

        /// <summary>
        /// Copies the elements of the collection to an array, starting at a particular array index. 
        /// </summary>
        /// <param name="array">The one-dimensional array that is the destination of the elements copied from collection. The array must have zero-based indexing.</param>
        /// <param name="index">The zero-based index in this collection at which copying begins.</param>
        /// <param name="arrayIndex">The zero-based index in array at which copying begins.</param>
        /// <param name="count">The number of elements to copy.</param>
        public void CopyTo(int index, Array array, int arrayIndex, int count)
        {
            Array.Copy(mItems, index, array, arrayIndex, count);
        }
        #endregion

        #region Count
        /// <summary>
        /// Gets the number of elements contained in the ICollection.
        /// </summary>
        public int Count
        { get { return mCount; } }
        #endregion

        #region Is Synchronized
        /// <summary>
        /// Gets a value indicating whether access to the ICollection is synchronized (thread safe).
        /// </summary>
        public bool IsSynchronized
        { get { return false; } }
        #endregion

        #region SyncRoot
        /// <summary>
        /// Gets an object that can be used to synchronize access to the ICollection.
        /// </summary>
        public object SyncRoot
        {
            get
            {
                if (mSynchRoot == null)
                {
                    System.Threading.Interlocked.CompareExchange(ref mSynchRoot, new object(), null);
                }
                return mSynchRoot;
            }
        }
        #endregion

        #endregion

        #region IEnumerable Members

        #region Get Enumerator
        /// <summary>
        /// Returns an enumerator that iterates through this collection
        /// </summary>
        public IEnumerator GetEnumerator()
        {
            return new ListboxItemListEnumerator(this);
        }

        #region Enumerator
        /// <summary>
        /// Strongly typed enumerator of ListboxItem.
        /// </summary>
        public class ListboxItemListEnumerator : object, System.Collections.IEnumerator
        {
            #region privates
            /// <summary>
            /// Current index
            /// </summary>
            private int mIndex;

            /// <summary>
            /// Current element pointed to.
            /// </summary>
            private ListBoxItem mCurrentElement;

            /// <summary>
            /// Collection to enumerate.
            /// </summary>
            private ListBoxItemList mCollection;
            #endregion

            #region Constructor
            /// <summary>
            /// Default constructor for enumerator.
            /// </summary>
            /// <param name="collection">Instance of the collection to enumerate.</param>
            internal ListboxItemListEnumerator(ListBoxItemList collection)
            {
                mIndex = -1;
                mCollection = collection;
            }
            #endregion

            #region Current
            /// <summary>
            /// Gets the ListboxItem object in the enumerated ListboxItemCollection currently indexed by this instance.
            /// </summary>
            public ListBoxItem Current
            { get { return mCurrentElement; } }

            /// <summary>
            /// Gets the current element in the collection.
            /// </summary>
            object System.Collections.IEnumerator.Current
            { get { return mCurrentElement; } }
            #endregion

            #region Reset
            /// <summary>
            /// Reset the cursor, so it points to the beginning of the enumerator.
            /// </summary>
            public void Reset()
            {
                mIndex = -1;
                mCurrentElement = null;
            }
            #endregion

            #region MoveNext
            /// <summary>
            /// Advances the enumerator to the next queue of the enumeration, if one is currently available.
            /// </summary>
            /// <returns>true, if the enumerator was succesfully advanced to the next queue; false, if the enumerator has reached the end of the enumeration.</returns>
            public bool MoveNext()
            {
                mIndex++;
                if (mIndex < mCollection.mCount)
                {
                    mCurrentElement = mCollection.mItems[mIndex];
                    return true;
                }
                mIndex = mCollection.mCount;
                return false;
            }
            #endregion
        }
        #endregion

        #endregion

        #endregion

        #region Constructors
        /// <summary>
        /// Default constructor.
        /// <para>Default capacity is 50</para>
        /// </summary>
        public ListBoxItemList()
        {
            mCapacity = 50;
            mItems = new ListBoxItem[mCapacity];
        }

        /// <summary>
        /// Create a new instance with the specified capacity
        /// </summary>
        public ListBoxItemList(int capacity)
        {
            mCapacity = capacity;
            mItems = new ListBoxItem[capacity];
        }
        #endregion
    }
}